﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Windows.Media;
using System.Xml;
using System.Xml.Linq;

namespace DynamicGeometry
{
    public interface ISerializer
    {
        string Write(IValueProvider value);
        void Read(IValueProvider value, string serialized);
        bool CanSerialize(IValueProvider value);
    }

    public abstract class SerializerBase<T> : ISerializer
    {
        public string Write(IValueProvider value)
        {
            return ToString(value.GetValue<T>());
        }

        public void Read(IValueProvider value, string serialized)
        {
            var deserialized = FromString(serialized);
            value.SetValue(deserialized);
        }

        public abstract string ToString(T value);
        public abstract T FromString(string str);

        public bool CanSerialize(IValueProvider value)
        {
            return value.Type == typeof(T);
        }
    }

    public static class SerializerExtensions
    {
        static List<ISerializer> serializers = Reflector.DiscoverTypesAndInstantiate<ISerializer>();
        static Dictionary<Type, ISerializer> serializerCache = new Dictionary<Type, ISerializer>();

        public static void Write(this ISerializer serializer, IValueProvider value, XmlWriter writer)
        {
            writer.WriteAttributeString(value.Name, serializer.Write(value));
        }

        public static void Read(this ISerializer serializer, IValueProvider value, XElement element)
        {
            var attribute = element.Attribute(value.Name);
            if (attribute == null)
            {
                return;
            }
            serializer.Read(value, attribute.Value);
        }

        public static void WriteValue(this IValueProvider value, XmlWriter writer)
        {
            foreach (var serializer in serializers)
            {
                if (serializer.CanSerialize(value))
                {
                    serializer.Write(value, writer);
                    return;
                }
            }
        }

        public static void ReadValue(this IValueProvider value, XElement element)
        {
            foreach (var serializer in serializers)
            {
                if (serializer.CanSerialize(value))
                {
                    serializer.Read(value, element);
                    return;
                }
            }
        }

        public static string WriteValue(this IValueProvider value)
        {
            var serializer = value.GetSerializerForValue();
            if (serializer != null)
            {
                return serializer.Write(value);
            }
            return "";
        }

        public static void ReadValue(this IValueProvider value, string serialized)
        {
            foreach (var serializer in serializers)
            {
                if (serializer.CanSerialize(value))
                {
                    serializer.Read(value, serialized);
                    return;
                }
            }
        }

        public static ISerializer GetSerializerForValue(this IValueProvider value)
        {
            ISerializer result = null;
            if (serializerCache.TryGetValue(value.Type, out result))
            {
                return result;
            }
            foreach (var serializer in serializers)
            {
                if (serializer.CanSerialize(value))
                {
                    result = serializer;
                    serializerCache.Add(value.Type, result);
                    break;
                }
            }
            return result;
        }
    }

    public class StringSerializer : SerializerBase<string>
    {
        public override string FromString(string str)
        {
            return str;
        }

        public override string ToString(string value)
        {
            return value;
        }
    }

    public class FontFamilySerializer : SerializerBase<FontFamily>
    {
        public override FontFamily FromString(string str)
        {
            return new FontFamily(str);
        }

        public override string ToString(FontFamily value)
        {
            return value.Source;
        }
    }

    public class BoolSerializer : SerializerBase<bool>
    {
        public override bool FromString(string str)
        {
            return bool.Parse(str.ToLower());
        }

        public override string ToString(bool value)
        {
            return value ? "true" : "false";
        }
    }

    public class ColorSerializer : SerializerBase<Color>
    {
        public override string ToString(Color value)
        {
            return value.ToString();
        }

        public override Color FromString(string str)
        {
            return str.ToColor();
        }
    }

    public class SimpleTypeSerializer : ISerializer
    {
        public string Write(IValueProvider value)
        {
            string result = "";
            var toStringMethod = GetToStringMethod(value);
            if (toStringMethod != null)
            {
                result = (string)toStringMethod.Invoke(value.GetValue<object>(), new object[] { CultureInfo.InvariantCulture });
            }
            else
            {
                result = value.GetValue<object>().ToString();
            }
            return result;
        }

        public void Read(IValueProvider value, string serialized)
        {
            var parseMethod = GetParseMethod(value);
            object result = null;
            if (parseMethod != null)
            {
                result = parseMethod.Invoke(null, new object[] { serialized, CultureInfo.InvariantCulture });
            }
            value.SetValue<object>(result);
        }

        static MethodInfo GetToStringMethod(IValueProvider value)
        {
            return value.Type.GetMethod("ToString", new[] { typeof(IFormatProvider) });
        }

        static MethodInfo GetParseMethod(IValueProvider value)
        {
            return value.Type.GetMethod("Parse", new[] { typeof(string), typeof(IFormatProvider) });
        }

        public bool CanSerialize(IValueProvider value)
        {
            return GetParseMethod(value) != null && GetToStringMethod(value) != null;
        }
    }
}